#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>

#define DEV_NAME "gpioled"
#define DEV_CNT 1
#define LED_ON 1
#define LED_OFF 0

char kbuf[128] = {0};

struct gpioled_dev
{
    dev_t dev_id;           // 设备编号
    struct cdev cdev;       // 内核字符设备
    struct class *class;    // 设备类
    struct device *device;  // 设备
    int major;              // 主设备
    int minor;              // 从设备
    struct device_node *nd; // 设备节点
    int led_gpio;
};

static struct gpioled_dev gpioled;

int led_open(struct inode *inode, struct file *file)
{
    file->private_data = &gpioled;

    printk("gpioled open ...\n");
    return 0;
}

ssize_t led_read(struct file *file, char __user *ubuf, size_t size, loff_t *offset)
{
    printk("gpioled read!\n");
    return 0;
}

ssize_t led_write(struct file *file, const char __user *ubuf, size_t size, loff_t *offset)
{
    unsigned char ret;
    struct gpioled_dev *dev = file->private_data;

    if (size > sizeof(kbuf))
    {
        size = sizeof(kbuf);
    }
    if (copy_from_user(kbuf, ubuf, size))
    {
        printk("gpioled copy data form user fail!\n");
        return -EIO;
    }

    ret = kbuf[0];
    if (ret >= 0x30)
        ret -= 0x30;

    if (ret == LED_ON)
    {
        gpio_set_value(dev->led_gpio, LED_ON);
    }
    else if (ret == LED_OFF)
    {
        gpio_set_value(dev->led_gpio, LED_OFF);
    }

    printk("gpioled write %d!\n", ret);
    return size;
}

int led_close(struct inode *inode, struct file *file)
{
    printk("gpioled close!\n");
    return 0;
}

struct file_operations gpioled_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_close};

static int ledcdev_probe(struct platform_device *pdv)
{
    int ret = 0;
    const char *str;

    // 读取设备树节点的属性数据
    // 获取设备节点：gpioled
    gpioled.nd = of_find_node_by_path("/gpioled");
    if (gpioled.nd == NULL)
    {
        printk("gpioled node find failed.\n");
        return -EINVAL;
    }
    else
    {
        printk("gpioled node find ok.\n");
    }

    // 获取 status 属性内容
    ret = of_property_read_string(gpioled.nd, "status", &str);
    if (ret < 0)
    {
        printk("gpioled status read failed!\n");
        return -EINVAL;
    }
    else
    {
        printk("gpioled status = %s\n", str);
    }

    if (strcmp(str, "okay"))
    {
        return -EINVAL;
    }
    // 获取 compatible 属性内容
    ret = of_property_read_string(gpioled.nd, "compatible", &str);
    if (ret < 0)
    {
        printk("gpioled compatible read failed!\n");
        return -EINVAL;
    }
    else
    {
        printk("gpioled compatible = %s\n", str);
    }

    if (strcmp(str, "zbl,led"))
    {
        printk("gpioled: Compatible match failed\n");
        return -EINVAL;
    }

    // 获取设备树中的 gpio 属性
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    if (gpioled.led_gpio < 0)
    {
        printk("gpioled led-gpio find failed.\n");
        return -EINVAL;
    }
    else
    {
        printk("gpioled led-gpio num=%d\n", gpioled.led_gpio);
    }
    // 向gpio子系统申请GPIO
    ret = gpio_request(gpioled.led_gpio, "LED-GPIO");
    if (ret)
    {
        printk("gpioled failed to request led-gpio\n");
        return ret;
    }

    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if (ret < 0)
    {
        printk("gpioled can't set gpio output mode.\n");
    }

    // 注册字符设备驱动
    // 创建设备号
    if (gpioled.major)
    {
        gpioled.dev_id = MKDEV(gpioled.major, 0);
        ret = register_chrdev_region(gpioled.dev_id, DEV_CNT, DEV_NAME);
        if (ret < 0)
        {
            printk("gpioled can't regsiter %s char driver [ret=%d]\n", DEV_NAME, ret);
            goto free_gpio;
        }
    }
    else
    {
        ret = alloc_chrdev_region(&gpioled.dev_id, 0, DEV_CNT, DEV_NAME);
        if (ret < 0)
        {
            printk("gpioled: %s can't alloc chrdev region, ret=%d.\n", DEV_NAME, ret);
            goto free_gpio;
        }
        gpioled.major = MAJOR(gpioled.dev_id);
        gpioled.minor = MINOR(gpioled.dev_id);
    }

    printk("gpioled: dtsled major=%d, minor=%d.\n", gpioled.major, gpioled.minor);
    // 关联结构体
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);

    ret = cdev_add(&gpioled.cdev, gpioled.dev_id, DEV_CNT);
    if (ret < 0)
    {
        goto del_unregister;
    }

    gpioled.class = class_create(THIS_MODULE, DEV_NAME);
    if (IS_ERR(gpioled.class))
    {
        goto del_cdev;
    }

    gpioled.device = device_create(gpioled.class, NULL, gpioled.dev_id, NULL, DEV_NAME);
    if (IS_ERR(gpioled.device))
    {
        goto destroy_class;
    }

    printk("gpioled init ...\n");
    return 0;
destroy_class:
    class_destroy(gpioled.class);
del_cdev:
    cdev_del(&gpioled.cdev);
del_unregister:
    unregister_chrdev_region(gpioled.dev_id, DEV_CNT);
free_gpio:
    gpio_free(gpioled.led_gpio);
    return -EIO;
}
static int ledcdev_remove(struct platform_device *pdv)
{

    printk("gpioled exit ...\n");

    gpio_set_value(gpioled.led_gpio, 1);

    cdev_del(&gpioled.cdev);

    unregister_chrdev_region(gpioled.dev_id, DEV_CNT);

    device_destroy(gpioled.class, gpioled.dev_id);

    class_destroy(gpioled.class);

    gpio_free(gpioled.led_gpio);
    return 0;
}

static const struct of_device_id led_match_table[] = {
    {.compatible = "zbl,led"},
    {},
};

static struct platform_driver test_gpio = {
    .remove = ledcdev_remove,
    .probe = ledcdev_probe,
    .driver = {
        .owner = THIS_MODULE,
        .of_match_table = led_match_table,
        .name = "led-test"},
};

static int __init gpioled_init(void)
{
    return platform_driver_register(&test_gpio);
}

static void __exit gpioled_exit(void)
{
    platform_driver_unregister(&test_gpio);
}

module_init(gpioled_init);

module_exit(gpioled_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zbl");
